package domain;

import interfaces.UserConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Vector;
import logger.ForumLogger;
import persistence.XMLUtils;
import domain.items.*;
import domain.users.*;

/**
 * this interface\class is some kind of a user-specific "controller". it
 * resembles the connection between a surfer and the system. in the technical
 * aspect, it represents the underlying system from the presentation layer's
 * point of view. also you can look at it as a "box" that every surfer receives
 * upon starting the program, whatever he sends\ receives goes through this box.
 */
public class WsepConnection implements UserConnection {


	private static final int LOGIN = 0;
	private static final int REGISTER = 1;
	private static final int LOGOUT = 2;
	private static final int GET_ROOT_TOPIC = 3;
	private static final int GET_ITEM = 4;
	private static final int ADD_POST = 5;
	private static final int UPDATE_POST = 6;
	private static final int DELETE_POST = 7;
	private static final int ADD_THREAD = 8;
	private static final int DEMOTE_USER = 9;
	private static final int PROMOTE_USER = 10;
	private static final int GET_FATHER = 11;
	private static final int SEARCH_BY_AUTHOR = 12;
	private static final int SEARCH_BY_DATE = 13;
	private static final int SEARCH_BY_CONTENT = 14;
	private static final int DELETE_USER = 15;
	private static final int USERS_STATUS = 16;
	private static final int UPDATE_USER = 17;

	private static final String DELIMITER = " \t ";

	/**
	 * a Connection instance relates to a single user.
	 */
	private User _user;

	/**
	 * the Item which is currently being viewed by the user.
	 */
	private Item _currentlyViewedItem;

	/**
	 * socket to communicate with server
	 */
	private Socket _clientSocket;

	/**
	 * input buffer
	 */
	private BufferedReader _in;

	/**
	 * output buffer
	 */
	private PrintWriter _out;

	/**
	 * constructor, with User instance as parameter to build the Connection
	 * around it.
	 *
	 * @param user
	 * @throws IOException
	 */
	public WsepConnection(User user) throws IOException {
		this._user = user;
	}

	/**
	 *
	 * @param readFromSocket
	 * @return
	 */
	private boolean returnBool(String readFromSocket) {
		if (readFromSocket.equals("true"))
			return true;
		else
			return false;
	}

	/**
	 * when the surfer chooses to login.
	 */
	public boolean login(String userName, String password) {
		String message = "" + LOGIN + DELIMITER + userName + DELIMITER + password;
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		String[] tokens = ans.split(DELIMITER);
		if (tokens[0].equals("MEMBER")) {
			this._user = (Member) XMLUtils.deserialize(tokens[1]);
			return true;
		}
		if (tokens[0].equals("ERROR")) {
			this.handleErrorMessage(tokens[1]);
			return false;
		} else {
			System.err.println("unexcpected reply from server!");
			return false;
		}
	}

	/**
	 * handles an error message coming from the server
	 *
	 * @param errorMessage
	 */
	private void handleErrorMessage(String errorMessage) {
		// System.err.println(errorMessage);
		ForumLogger.logGeneral(errorMessage);
		System.out.println(errorMessage);
	}

	/**
	 * adding a new post.
	 */
	public boolean addPost(String subject, String text) {
		String message = "" + ADD_POST + DELIMITER + subject + DELIMITER + text + DELIMITER
				+ ((Member) this._user).getUserName();
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		String tokens[] = ans.split(DELIMITER);
		if (tokens[0].equals("true")) {
			((ThreadPacket) this._currentlyViewedItem).getPosts().add(
					new PostPacket(((Member) this._user).getUserName(), subject, text,new Date()));
			return true;
		}
		if (tokens[0].equals("ERROR")) {
			this.handleErrorMessage(tokens[1]);
			return false;
		} else {
			System.err.println("unrecognized reply from server!");
			System.err.println(tokens[0]);
			return false;
		}
	}

	/**
	 * modifying a post.
	 */
	public boolean updatePost(String newSubject, String newText) {
		String messege = "" + UPDATE_POST + DELIMITER + newSubject + DELIMITER + newText;
		this.sendToSocket(messege);
		String ans = this.readFromSocket();
		String[] ansTokens = ans.split(DELIMITER);
		if (ansTokens[0].equals("true")) {
			((PostPacket) _currentlyViewedItem).setName(newSubject);
			((PostPacket) _currentlyViewedItem).set_content(newText);
			return true;
		}
		if (ansTokens[0].equals("ERROR")) {
			this.handleErrorMessage(ansTokens[1]);
			return false;
		} else {
			System.err.println("unrecognized server reply!");
			return false;
		}

	}

	/**
	 * delete a post from the repository
	 */
	public boolean deletePost() {
		this.sendToSocket("" + DELETE_POST);
		String ans = this.readFromSocket();
		String[] ansTokens = ans.split(DELIMITER);
		if (ansTokens[0].equals("true")) {
			return true;
		}
		if (ansTokens[0].equals("ERROR")) {
			this.handleErrorMessage(ansTokens[1]);
			return false;
		} else {
			System.err.println("unrecognized server reply!");
			return false;
		}
	}

	/**
	 * when user wants to logout.
	 */
	public boolean logout() {

		this.sendToSocket(""+LOGOUT);
		String ans = this.readFromSocket();
		String[] ansTokens = ans.split(DELIMITER);
		if(ansTokens[0].equals("true")) {
			ForumLogger.logLogout(((Member) this._user).getUserName());
			this._user = new Guest();
			this.setCurrentlyViewedItem(getRootTopic());
			return true;
		}
		if (ansTokens[0].equals("ERROR")) {
			this.handleErrorMessage(ansTokens[1]); return false; } else {
				System.err.println("unrecognized server reply!"); return false; }
	}

	/**
	 * in case some info of the connected user is needed
	 */
	public User getUser() {
		// TODO Auto-generated method stub
		return this._user;
	}

	/**
	 * get the starting point for navigating in the forum
	 */
	public TopicPacket getRootTopic() {

		//System.out.println("inside WsepConnection.getRootTopic();");
		String message = "" + WsepConnection.GET_ROOT_TOPIC;
		this.sendToSocket(message);
		//System.out.println("waiting for server...");
		String ans = this.readFromSocket();
		//System.out.println("received server response.");
		String[] ansTokens = ans.split(DELIMITER);
		if (ansTokens[0].equals("ITEM")) {
			this._currentlyViewedItem = (TopicPacket) XMLUtils.deserialize(ansTokens[1]);
			this._currentlyViewedItem.setName("Root Page");
		}
		if (ansTokens[0].equals("ERROR")) {
			this.handleErrorMessage(ansTokens[1]);
		}
		return (TopicPacket) this._currentlyViewedItem;
	}

	/**
	 * when the surfer wants to add himself to the user repository.
	 */
	public boolean registerUser(String userName, String email,String password) {
		String message = "" + REGISTER + DELIMITER + userName + DELIMITER + email +
							DELIMITER + password;
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		String ansTokens[] = ans.split(DELIMITER);
		if (ansTokens[0].equals("true")) {
			return true;
		}
		if (ansTokens[0].equals("ERROR")) {
			this.handleErrorMessage(ansTokens[1]);
			return false;
		} else {
			System.err.println("unrecognized server reply");
			return false;
		}
	}

	/**
	 * add a new thread.
	 */
	public boolean addThread(String subject, String newMessageText) {
		String message = "" + ADD_THREAD + DELIMITER + subject + DELIMITER + newMessageText + DELIMITER
				+ ((Member) this._user).getUserName();
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		String[] ansTokens = ans.split(DELIMITER);
		if (ansTokens[0].equals("true")) {
			((TopicPacket) this._currentlyViewedItem).getSubItems().add(subject);
			return true;
		}
		if ((ansTokens.length>1) &&(ansTokens[0].equals("ERROR"))) {
			this.handleErrorMessage(ansTokens[1]);
			return false;
		} else {
			System.err.println("unrecognized server reply");
			return false;
		}

	}

	/**
	 * getters & setters.
	 */
	public Item getCurrentlyViewedItem() {
		return this._currentlyViewedItem;
	}

	/**
	 * get sub-item by choice
	 */
	public Item getItem(int choice) {
		if (this._currentlyViewedItem.getType() == domain.items.Item.PACKET) {
			return getItemFromServer(choice);
		} else {
			System.out.println("WTF????");
			return null;
		}
	}

	/**
	 * get item from server by choice
	 *
	 * @param choice
	 * @return
	 */
	private Item getItemFromServer(int choice) {
		String message = "" + WsepConnection.GET_ITEM + DELIMITER + choice;
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		if (ans.equals("")) {
			System.err.println("connection with server is lost!");
			System.err.println("Run client again.");
			ForumLogger.logIOException();
			System.exit(0);
			return null;
		} else {
			return (Item) XMLUtils.deserialize(ans);
		}
	}

	/**
	 * get connected user's rank
	 */
	public int getUserRank() {
		return this._user.getRank();
	}

	/**
	 * set the currently viewed item
	 */
	public boolean setCurrentlyViewedItem(Item item) {
		try {
			this._currentlyViewedItem = item;
			return true;
		} catch (Exception e) {
			System.err.println("Error: with setting a new currently viewed Item");
			return false;
		}
	}

	/**
	 * decrement user rank by 1
	 */

	public boolean demoteUser(String username) {
		String message = "" + DEMOTE_USER + DELIMITER + username;
		this.sendToSocket(message);
		return this.returnBool(this.readFromSocket());
	}

	/**
	 * increment user rank by 1
	 */

	public boolean PromoteUser(String username) {
		String message = "" + PROMOTE_USER + DELIMITER + username;
		this.sendToSocket(message);
		return this.returnBool(this.readFromSocket());
	}

	/**
	 * makes a connection to chosen host name & port
	 */
	public void connect(String hostname, int port) throws UnknownHostException, IOException {
		try {
			this._clientSocket = new Socket(hostname, port);
		} catch (ConnectException e) {
			System.out.println("run server first!");
			ForumLogger.logIOException();
			System.exit(1);
		}
		this._in = new BufferedReader(new InputStreamReader(this._clientSocket.getInputStream()));
		this._out = new PrintWriter(this._clientSocket.getOutputStream(), true);
		this._out.flush();

	}

	/**
	 * blocking method! waits for an incoming string from the socket.
	 *
	 * @return
	 */
	public String readFromSocket() {
		String response = "";
		char[] buffer = new char[10000];
		int charsRead = -2;
		try {
			while ((charsRead = this._in.read(buffer)) != -1) {
				if (charsRead <= 0)
					continue;
				response = new String(buffer, 0, charsRead);
				break;
			}
		} catch (IOException e) {
		}
		return response;

	}

	/**
	 * sends a string to the socket.
	 *
	 * @param string
	 */
	public void sendToSocket(String string) {
		this._out.print(string);
		this._out.flush();
	}

	/**
	 * get current item's father for stepping back
	 */
	public Item getFather() {
		String message = "" + GET_FATHER;
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		String[] tokens = ans.split(DELIMITER);
		if (tokens[0].equals("ITEM")) {
			this._currentlyViewedItem = (Item) XMLUtils.deserialize(tokens[1]);
			return this._currentlyViewedItem;
		}
		if (tokens[0].equals("NULL")) {
			return this._currentlyViewedItem;
		}
		if (tokens[0].equals("ERROR")) {
			this.handleErrorMessage(tokens[1]);
			return this._currentlyViewedItem;
		}
		return this._currentlyViewedItem;
	}

	/**
	 * this method is interfacing the search engine to the UI (need to add error
	 * catching)
	 */
	@SuppressWarnings("unchecked")
	public Vector<PostPacket> searchByAuthor(String authorName) {
		String message = "" + SEARCH_BY_AUTHOR + DELIMITER + authorName;
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		return (Vector<PostPacket>) XMLUtils.deserialize(ans);
	}

	/**
	 * this method is interfacing the search engine to the UI (need to add error
	 * catching)
	 */
	@SuppressWarnings("unchecked")
	public Vector<PostPacket> searchByContent(String sentence) {
		String message = "" + SEARCH_BY_CONTENT + DELIMITER + sentence;
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		return (Vector<PostPacket>) XMLUtils.deserialize(ans);
	}

	/**
	 * this method is interfacing the search engine to the UI (need to add error
	 * catching)
	 */
	@SuppressWarnings("unchecked")
	public Vector<PostPacket> searchByDate(String fromDate, String toDate) {
		String message = "" + SEARCH_BY_DATE + DELIMITER + fromDate + DELIMITER + toDate;
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		return (Vector<PostPacket>) XMLUtils.deserialize(ans);
	}

	public boolean deleteUser(String username) {
		boolean reply = false;
		String message = "" + DELETE_USER + DELIMITER + username;
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		String[] tokens = ans.split(DELIMITER);
		if (tokens[0].equals("true")) {
			reply = true;
		}
		if (tokens[0].equals("ERROR")) {
			reply = false;
		}
		return reply;
	}


	public boolean updateUser(String newEmail, String oldPass, String newPass) {
		boolean reply = false;
		String message = "" + UPDATE_USER + DELIMITER + ((Member)_user).getUserName()
		+ DELIMITER + newEmail + DELIMITER + oldPass + DELIMITER + newPass ;
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		String[] tokens = ans.split(DELIMITER);
		if (tokens[0].equals("true")) {
			reply = true;
		}
		if (tokens[0].equals("ERROR")) {
			reply = false;
		}
		return reply;
	}


	public String getStatus() {
		String message = "" + USERS_STATUS;
		this.sendToSocket(message);
		String ans = this.readFromSocket();
		return ans;
	}



}
